/**
 * Copyright (C) 2017 - 2020 https://github.com/joewee
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bifrost.web.controller;


import com.alibaba.fastjson.JSON;
import com.bifrost.annotation.Authorization;
import com.bifrost.annotation.CurrentUser;
import com.bifrost.common.constant.SessionConstant;
import com.bifrost.entity.Customer;
import com.bifrost.entity.Order;
import com.bifrost.entity.OrderTransaction;
import com.bifrost.event.OrderPaidEvent;
import com.bifrost.exception.CancelOrderException;
import com.bifrost.common.model.ResultModel;
import com.bifrost.repository.OrderTransactionRepository;
import com.bifrost.repository.RefundRequestRepository;
import com.bifrost.service.CustomerService;
import com.bifrost.service.OrderService;
import com.bifrost.service.PayGatewayService;
import com.bifrost.service.RefundRequestService;
import com.bifrost.util.IPUtils;
import com.bifrost.util.XmlUtils;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;

/**
 * @author joewee
 * @version 1.0.0
 * @Date 2017/11/25 15:22
 */
@Controller
@RequestMapping("wechat/pay")
public class WechatPayController implements ApplicationContextAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(WechatPayController.class);
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    @Autowired
    OrderService orderService;
    @Autowired
    WxPayService wxPayService;
    @Autowired
    PayGatewayService wechatPayService;
    @Autowired
    CustomerService customerService;
    @Value("${wechat.pay.mchKey:#{null}}")
    private String mchKey;
    @Value("${wechat.pay.notify.url:#{null}}")
    private String notifyUrl;
    @Value("${wechat.order.expire:#{30}}")
    private long expire;
    @Autowired
    OrderTransactionRepository orderTransactionRepository;
    @Autowired
    RefundRequestRepository refundRequestRepository;
    @Autowired
    RefundRequestService refundRequestService;
    protected ApplicationContext applicationContext;

    @GetMapping("/notify")
    public void notifyByGet(HttpServletRequest req, HttpServletResponse resp) {
        notifyByPost(req, resp);
    }

    @PostMapping("/notify")
    public void notifyByPost(HttpServletRequest request, HttpServletResponse response) {
        try {
            synchronized (this) {
                Map<String, String> kvm = XmlUtils.parseRequestXmlToMap(request);
                Order order = orderService.findOrderByOutTradeNo(kvm.get("out_trade_no").toString());
                if (SignUtils.checkSign(kvm, null, mchKey)) {
                    if (kvm.get("return_code").equals("SUCCESS")) {
                        //TODO(user) 微信服务器通知此回调接口支付成功后，通知给业务系统做处理
                        String totalFee = kvm.getOrDefault("total_fee", "0");
                        OrderPaidEvent orderPaidEvent = new OrderPaidEvent(kvm.get("out_trade_no"), new BigInteger(totalFee));
                        applicationContext.publishEvent(orderPaidEvent);
                        //记录交易日志
                        OrderTransaction transaction = new OrderTransaction();
                        transaction.setOrder(order);
                        transaction.setPaymentType(order.getPayment().getDescription());
                        transaction.setTransactionAmount(totalFee);
                        transaction.setTransactionDate(new Date());
                        transaction.setTransactionType("notify");
                        transaction.setBusinessId(kvm.get("transaction_id"));
                        transaction.setUpdatedTimestamp(new Date());
                        transaction.setTransactionDetails(String.format("交易成功：%s", JSON.toJSONString(kvm)));
                        orderTransactionRepository.save(transaction);

                        LOGGER.info("out_trade_no: " + kvm.get("out_trade_no") + " pay SUCCESS!");
                        response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[ok]]></return_msg></xml>");
                    } else {
                        LOGGER.error("out_trade_no: "
                                + kvm.get("out_trade_no") + " result_code is FAIL");
                        response.getWriter().write(
                                "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[result_code is FAIL]]></return_msg></xml>");
                    }
                } else {
                    response.getWriter().write(
                            "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[check signature FAIL]]></return_msg></xml>");
                    LOGGER.error("out_trade_no: " + kvm.get("out_trade_no")
                            + " check signature FAIL");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Authorization
    @PostMapping("/unifiedorder")
    @ResponseBody
    public ResultModel unifiedOrder(@CurrentUser Long userId, @RequestParam String out_trade_no, @RequestParam int total_fee, @RequestParam String body, HttpServletRequest req, HttpServletResponse resp) {
        String spbill_create_ip = IPUtils.getIpAddr(req);
        StringBuilder msg = new StringBuilder();
        HttpSession session = req.getSession(true);
        Object openId = session.getAttribute(SessionConstant.OPEN_ID);
        if (openId == null) {
            return ResultModel.error(String.format("长时间未操作登录会话超时"));
        }
        LOGGER.info("openid:" + openId.toString());
        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder().body(StringUtils.substring(body, 0, 63))
                .spbillCreateIp(spbill_create_ip)
                .totalFee(total_fee)
                .tradeType(WxPayConstants.TradeType.JSAPI)
                .outTradeNo(out_trade_no)
                .openid(openId.toString())
                .timeStart(String.format("%s", LocalDateTime.now().format(FORMATTER)))
                .timeExpire(String.format("%s", LocalDateTime.now().plus(expire, ChronoUnit.MINUTES).format(FORMATTER)))
                .notifyUrl(notifyUrl).build();
        try {
            Map parameters = wechatPayService.unifiedOrder(request);
            //返回授权
            return ResultModel.ok("成功").put("payInfo", parameters);
        } catch (WxPayException e) {
            LOGGER.error(e.getMessage(), e);
            msg.append(e.getErrCode()).append(":").append(e.getErrCodeDes()).append("\n")
                    .append(e.getReturnCode()).append(":").append(e.getReturnMsg()).append("\n")
                    .append(e.getResultCode());
        }
        return ResultModel.error(String.format("支付失败:%s", msg.toString()));
    }

    @Authorization
    @ApiOperation("申请退款")
    @PostMapping("/refund")
    @ResponseBody
    public ResultModel refund(@CurrentUser Long  userId, @RequestParam Long orderId) throws CancelOrderException {

        try {
            Customer customer = customerService.findCustomer(userId);
            orderService.cancel(customer, orderId, "用户申请退款");
            return ResultModel.ok("生成退款申请成功");
        }catch (CancelOrderException e){
            return ResultModel.error("订单申请退款失败："+e.getErrorMessage());
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
